﻿package {
	
	import flash.display.*;
	import flash.events.*;
	import flash.geom.Point;
	
	/* This class is used to represent a segment drawn on the drawing panel */
	public class DrawSegment extends MovieClip {
		public static const SEGMENT_SIZE:Number = 6;
		public static const PARALLEL_DISCRIM_THRESH:Number = .01; // The minimum difference (in degrees) that we adjust a line to match the slope of another line
		public static const LENGTH_DISCRIM_THRESH:Number = .2; //The minimum difference (in pixels) that we dilate a line to match the length of another line
		public static const SEGMENT_COLOR:uint = 0xAA8844;
		public static const OUTLINE_COLOR:uint = 0x0000FF;
		public static const ENHANCE_COLOR:uint = 0x444444;
		public static const FIXED_COLOR:uint = 0x774400;
		public static const PINNED_COLOR:uint = 0x444444;
		
		public static const BACK_HEIGHT:int = 20;
		public var id:String;
		public var fixed:Boolean;
		// private objects
		private var _point1:DrawPoint,_point2:DrawPoint;
		private var backShape:Shape, frontShape:Shape;
		private var _congruentPartners:Array = null;
		private var _parallelPartners:Array = null;
		private var congruentMarking:Shape = null;
		private var parallelMarking:Shape = null;
		// private vars
		private var segmentsize:Number;
		private var _outline:Boolean=false;
		private var _enhance:Boolean=false;
		private var _pinned:Boolean=false;
		private var _congruentCount:uint = 0; // how many markings
		private var _parallelCount:uint = 0;		
		
		public function get congruentCount():uint{return _congruentCount;}
		public function get parallelCount():uint{return _parallelCount;}
		public function get congruentPartners():Array{return _congruentPartners;}
		public function get parallelPartners():Array{return _parallelPartners;}
		public function get pinned():Boolean{if (_point1.pinned&&_point2.pinned){_pinned=true;return true;}else{_pinned=false;return false;}} 
		public function get midpoint():Point{return new Point((endpoint1.x+endpoint2.x)/2, (endpoint1.y+endpoint2.y)/2)}
		public function get length():Number{return Math.sqrt(Math.pow(endpoint1.x-endpoint2.x,2) + Math.pow(endpoint1.y-endpoint2.y,2))}
		public function get point1():DrawPoint{return _point1}
		public function get point2():DrawPoint{return _point2}
		public function get endpoint1():Point{return _point1.point}
		public function get endpoint2():Point{return _point2.point}
		public function get outline():Boolean{return _outline}
		public function set outline(b:Boolean):void{_outline = b; redraw();}
		public function get enhanced():Boolean{return _enhance}
		public function set enhance(b:Boolean):void{_enhance = b; redraw();}
		// angle from endpoint2 to endpoint 1, 0 degrees is vertical , 90 is horizontal 
		public function get angle():Number{return MathUtilities.calcAngle(endpoint2, endpoint1);}
		override public function get rotation():Number{return MathUtilities.calcRotation(endpoint2, endpoint1);}
		// these get the rotation and angle specifically from or towards given point
		public function rotationFrom(p:DrawPoint):Number
		{
			if (p == point1){return MathUtilities.calcRotation(endpoint2, endpoint1);} 
			else if (p == point2){return MathUtilities.calcRotation(endpoint1, endpoint2);}
			return -999;
		}
		public function angleFrom(p:DrawPoint):Number
		{
			if (p == point1){return MathUtilities.calcAngle(endpoint2, endpoint1);} 
			else if (p == point2){return MathUtilities.calcAngle(endpoint1, endpoint2);}
			return -999;
		}
		public function rotationTo(p:DrawPoint):Number
		{
			if (p == point1){return MathUtilities.calcRotation(endpoint1, endpoint2);} 
			else if (p == point2){return MathUtilities.calcRotation(endpoint2, endpoint1);}
			return -999;
		}
		public function angleTo(p:DrawPoint):Number
		{
			if (p == point1){return MathUtilities.calcAngle(endpoint1, endpoint2);} 
			else if (p == point2){return MathUtilities.calcAngle(endpoint2, endpoint1);}
			return -999;
		}
		public function get angleAbs():Number{return (MathUtilities.calcAngle(endpoint2, endpoint1)+Math.PI)%Math.PI;}
		public function get rotationAbs():Number{return (MathUtilities.calcRotation(endpoint2, endpoint1)+180)%180;}
		
		/** If point1 and point2 are given then just draw the segment, that's it
			If point2 is NOT given the segment is open and changes until externally told to stop
		*/
		public function DrawSegment(_point1:DrawPoint, _point2:DrawPoint, id:String="", fixed:Boolean=false)
		{
			this.segmentsize = ShapeProductionTask.unit*SEGMENT_SIZE;
			this._point1 = _point1;
			this._point2 = _point2;
			this._point1.addSegment(this);
			this._point2.addSegment(this);
			this.fixed = fixed;
			//if (_point1.fixed && _point2.fixed) this.fixed=true;
			this.mouseEnabled = false;
			_parallelPartners = new Array();
			_congruentPartners = new Array();			
			this.id = id;
			//trace("new segment", id);
			// add shapes
			backShape = new Shape();
			frontShape = new Shape();
			this.addChild(backShape);
			this.addChild(frontShape);
			redraw();
			
		} 
		/** Is this segment adjacent to the given segment (i.e., attached to the same draw point */
		public function isAdjacentTo (ds:DrawSegment):Boolean
		{
			if (this==ds) return false;
			if (this.point1 == ds.point1 || this.point1 == ds.point2 || this.point2 == ds.point1 || this.point2 == ds.point2)
			{
				return true;
			} else
			{
				return false;
			}
		}
		public function containsDrawPoint(drawpoint:DrawPoint):Boolean
		{
			if (drawpoint == point1 || drawpoint == point2)
			{
				return true;
			} else
			{
				return false;
			}
		}
		public function containsPoint(point:Point):Boolean
		{
			if (point.x == point1.x && point.y == point1.y || point.x == point2.x && point.y == point2.y)
			{
				return true;
			} else
			{
				return false;
			}
		}
		public function otherDrawPoint(drawpoint:DrawPoint):DrawPoint
		{
			if (drawpoint == point1)
			{
				return point2;
			} else if (drawpoint == point2)
			{
				return point1;
			} else
			{
				return null;
			}
		}
		public function otherPoint(point:Point):Point
		{
			if (point.x == point1.x && point.y == point1.y)
			{
				return endpoint2;	
			} else if (point.x == point2.x && point.y == point2.y)
			{
				return endpoint1;
			} else 
			{
				return null;
			}
		}
		public function intersectsDrawSegment(ds:DrawSegment, includeEndpoints:Boolean=false):Boolean
		{
			if (this==ds){ return false; }
			return (intersectsBetweenPoints(ds.point1.point, ds.point2.point, includeEndpoints));	
		}
		/** Does this segment intersect with a given drawSegment (except at endpoints)? */
		public function intersectsBetweenDrawPoints(dp1:DrawPoint, dp2:DrawPoint, includeEndpoints:Boolean=false):Boolean
		{
			return (intersectsBetweenPoints(dp1.point, dp2.point, includeEndpoints));			
		}
		/** Does this segment intersect with a given drawSegment (except at endpoints)? */
		public function intersectsBetweenPoints(p1:Point, p2:Point, includeEndpoints:Boolean=false):Boolean
		{
			var ds1dp1:Point = this.point1.point;
			var ds1dp2:Point = this.point2.point;
			var ds2dp1:Point = p1;
			var ds2dp2:Point = p2;
			var m1:Number = MathUtilities.slopePoint(ds1dp1, ds1dp2);
			var m2:Number = MathUtilities.slopePoint(ds2dp1, ds2dp2);
			//trace(m1, m2);
			//trace(this==ds, ds1dp1.x, ds1dp2.x, ds2dp1.x, ds2dp2.x);
			if (m1 == m2){ return false; }
			else
			{
				var i:Point = MathUtilities.intersectionSegments(ds1dp1,ds1dp2, ds2dp1, ds2dp2, includeEndpoints);
				//trace (i);
				if (i != null)
				{
					return true;
				} else
				{
					return false;
				}
			}
		}
		/** This function is used the panel class to redraw from point to point */
		public function redraw (dp:DrawPoint = null):void
		{
			// we won't bother if the given drawpoint is not included
			if (dp == null || containsDrawPoint(dp))
			{
				backShape.graphics.clear();
				frontShape.graphics.clear();
				backShape.graphics.beginFill(0xCCCCCC,0);
				backShape.graphics.drawRect(-BACK_HEIGHT/2, -length, BACK_HEIGHT, length);
				backShape.graphics.endFill();
				backShape.x = endpoint1.x;
				backShape.y = endpoint1.y;
				backShape.rotation = rotation;
				pinned; // check to see if we are pinned
				
				if (outline)
				{
					frontShape.graphics.lineStyle(segmentsize, OUTLINE_COLOR);
				} else if (enhanced)
				{
					frontShape.graphics.lineStyle(segmentsize, ENHANCE_COLOR);
				} else if (fixed)
				{
					frontShape.graphics.lineStyle(segmentsize, FIXED_COLOR);
				} else 
				{
					frontShape.graphics.lineStyle(segmentsize, SEGMENT_COLOR);
				}
				frontShape.graphics.lineTo(0, -length);	
				frontShape.x = endpoint1.x;
				frontShape.y = endpoint1.y;
				frontShape.rotation = rotation;		
				
				this.congruentCount = congruentCount;
				this.parallelCount = parallelCount;
				
				// if pinned, draw pins along length of segment
				if (pinned)
				{
					var pcount:uint = Math.floor(this.length/(8*DrawSegment.SEGMENT_SIZE/2));
					frontShape.graphics.lineStyle(0);
					for (var i:uint=0; i < pcount; i++)
					{
						frontShape.graphics.beginFill(0xFFFFFF);
						frontShape.graphics.drawCircle(0, -length*i/pcount, DrawSegment.SEGMENT_SIZE/2);
						frontShape.graphics.endFill();
					}
					
				}
				
			}
		}
		/** Checks to see if there is enough room to adjust, then adjusts */
		public function dilateTo(pixels:Number, percent:Number=1, dpoint:DrawPoint=null):Boolean
		{
			var d:Number;
			if (Math.abs(pixels-length) > LENGTH_DISCRIM_THRESH)
			{				
				d = percent*(pixels-length);
				if(d!=0) dilate(d, dpoint);
				return true;
			} else
			{
				d = (pixels-length);
				if(d!=0) dilate(d, dpoint);
				return false;
			}
		}
		/** Dilate a number of pixels (technically should be a multiplier, but screw it) 
		 * point represents the point that is moving, if null then move both endpoints by half
		 * */
		public function dilate(pixels:Number, dpoint:DrawPoint=null):void
		{
			//dilate half from both ends.
			var p1:Point = endpoint1;
			var p2:Point = endpoint2;
			var p1n:Point, p2n:Point;
			if (dpoint == null)
			{
				p1n = MathUtilities.calcPointDistFromPoints(pixels/2,p1,p2);
				p2n = MathUtilities.calcPointDistFromPoints(pixels/2,p2,p1);
			} else if (dpoint == point1)
			{
				p1n = MathUtilities.calcPointDistFromPoints(pixels,p1,p2);
				p2n = p2;
			} else if (dpoint == point2)
			{
				p2n = MathUtilities.calcPointDistFromPoints(pixels,p2,p1);
				p1n = p1;		
			} else 
			{
				//trace("in drawsegment dilate,", dpoint.point, "needs to be a point of this segment, such as",p1,"or", p2); 
				return;
			}
			if (_point1.moveTo(p1n.x, p1n.y))
			{
				point1.redrawSegments();
				point1.redrawAngles();
				
			}
			if (_point2.moveTo(p2n.x, p2n.y))
			{
				point2.redrawSegments();
				point2.redrawAngles();
			}
			
		}
		/** Returns whether we have reached threshold already */
		public function rotateToDegrees(deg:Number, percent:Number=1, point:Point=null):Boolean
		{
			deg = (deg+180)%180;
			var diff:Number = deg-rotationAbs;
			if (Math.abs(diff) < 90)
			{ // normal case
				
			} else
			{ // case like 3, 177 (both close to vertical)
				// this rotation is > than target, e.g. 177 to 3, rotate positively
				if (rotationAbs > deg)
				{
					diff = deg + (180 - rotationAbs);
				} else
				{ // this rotation < than target, e.g. 3 to 177, rotate negatively
					diff = -1*(rotationAbs + (180 - deg));	
				}
			}
			var d:Number;	
			if (Math.abs(diff) > PARALLEL_DISCRIM_THRESH)
			{
				d = percent*diff;
				if (d!=0) rotateDegrees(d, point);
				return true;
			} else 
			{
				d = diff;
				if (d!=0) rotateDegrees(d, point);
				return false;
			}			
		}
		/** Returns whether we are rotating or not */
		public function rotateToRadians(ang:Number, percent:Number=1, point:Point=null):Boolean
		{
			if (Math.abs(((ang+Math.PI)%Math.PI-angleAbs)) > PARALLEL_DISCRIM_THRESH/180*Math.PI)
			{
				var d:Number = percent*((ang+Math.PI)%Math.PI-angleAbs);
				if (d!=0) rotateRadians(d, point);
				return true;
			}else 
			{
				return false;
			}			
		}
		/** rotate about the point */
		public function rotateDegrees(deg:Number, point:Point=null):void
		{
			if (point == null) point = midpoint;
			rotateRadians(deg*Math.PI/180, point);
		}
		/** rotate about the point */
		public function rotateRadians(rad:Number, point:Point=null):void
		{
			if (point == null) point = midpoint;
			// translate endpoints based on point so that we are rotating about zero
			var e1x:Number = point1.x - point.x;
			var e1y:Number = point1.y - point.y;
			var e2x:Number = point2.x - point.x;
			var e2y:Number = point2.y - point.y;
			
			var e1xn:Number = e1x*Math.cos(rad) - e1y*Math.sin(rad);
			var e1yn:Number = e1x*Math.sin(rad) + e1y*Math.cos(rad);
			var e2xn:Number = e2x*Math.cos(rad) - e2y*Math.sin(rad);
			var e2yn:Number = e2x*Math.sin(rad) + e2y*Math.cos(rad);
					
			if (_point1.moveTo(e1xn + point.x, e1yn + point.y))
			{
				point1.redrawSegments();
				point1.redrawAngles();
			}
			if (_point2.moveTo(e2xn + point.x, e2yn + point.y))
			{
				point2.redrawSegments();
				point2.redrawAngles();
			}		
		}
		
		/** Adds a new partner to this segments list of congruent partners.  If already exists return false, else return true; 
		 * Also, updates the count to reflect the minimum of all partners.
		 * */
		public function addCongruentPartner(ds:DrawSegment, count:uint=0):uint
		{
			if (count==0) count = congruentCount;
			var found:Boolean = false;
			for each (var _ds:DrawSegment in this.congruentPartners)
			{
				if (_ds == ds) 
				{
					found = true;
				}
			}
			if (!found)
			{
				congruentPartners.push(ds);
				// do converse
				ds.addCongruentPartner(this, count);
			}
			return setMinimumCongruentCount(count);	
		}
		/** Removes the Draw Segment from this objects collection, does not do converse */
		public function removeCongruentPartner(ds:DrawSegment):Boolean
		{
			var i:int = this.congruentPartners.indexOf(ds);
			if (i >= 0)
			{
				this.congruentPartners.splice(i,1);
				return true;			
			}
			return false;
		}
		/** Go through all partners and make sure that all congruent counts are the same, and minimized */
		public function setMinimumCongruentCount (count:uint=0):uint
		{
			if (congruentPartners.length > 0)
			{
				var minCount:uint;
				if (count == 0)
				{
					minCount = congruentCount;
				} else if(congruentCount == 0)
				{
					minCount = count;
				} else 
				{	
					minCount = Math.min(count, congruentCount);
				}
				//trace("count", count,"congruentCount",  congruentCount, "minCount", minCount)
				for each (var _ds:DrawSegment in this.congruentPartners)
				{
					//trace("count of segment", _ds.congruentCount);
					if (_ds.congruentCount < minCount && _ds.congruentCount > 0) minCount = _ds.congruentCount;
				}
				this.congruentCount = minCount;
				for each (_ds in this.congruentPartners){ _ds.congruentCount = minCount; }
				//trace("Minimum count of segment", minCount);
				return minCount;
			} else
			{
				this.congruentCount = 0;
				return 0;
			}
		}
		
		/** Adds a new partner to this segments list of parallel partners.  If already exists return false, else return true; 
		 * Also, updates the count to reflect the minimum of all partners.
		 * */
		public function addParallelPartner(ds:DrawSegment, count:uint=0):uint
		{
			if (count == 0) count = congruentCount;
			
			var found:Boolean = false;
			for each (var _ds:DrawSegment in this.parallelPartners)
			{
				if (_ds == ds) 
				{
					found = true;
				}
			}
			if (!found)
			{
				parallelPartners.push(ds);
				// do converse
				ds.addParallelPartner(this, count);
			}
			return setMinimumParallelCount(count);			
		}
		/** Removes the Draw Segment from this objects collection, does not do converse */
		public function removeParallelPartner(ds:DrawSegment):Boolean
		{
			var i:int = this.parallelPartners.indexOf(ds);
			if (i >= 0)
			{
				this.parallelPartners.splice(i,1);
				return true;			
			}
			return false;
		}
		/** Go through all partners and make sure that all congruent counts are the same, and minimized 
		 * the excluding parameter is used when we are breaking up a partner, to ensure that ex-partners don't have the same count
		 * */
		public function setMinimumParallelCount (count:uint=0, excluding:uint=0):uint
		{
			if (this.parallelPartners.length > 0)
			{
				var minCount:uint;
				if (count == 0)
				{
					minCount = parallelCount;
				} else if(parallelCount == 0)
				{
					minCount = count;
				} else 
				{	
					minCount = Math.min(count, parallelCount);
				}
				for each (var _ds:DrawSegment in this.parallelPartners)
				{
					if (_ds.parallelCount < minCount && _ds.parallelCount > 0) minCount = _ds.parallelCount;
				}
				this.parallelCount = minCount;
				for each (_ds in this.parallelPartners){ _ds.parallelCount = minCount; }
				
				return minCount;
			} else 
			{
				this.parallelCount = 0;
				return 0;
			}
		}
				
		/** Looks for all congruencies with this segment and checks if they are still valid, use threshold to determine congruency.
		 * Returns true if a change has been made */
		public function checkExistingCongruencies(threshold:Number):Boolean
		{
			var changed:Boolean = false;
			for each (var _ds:DrawSegment in this.congruentPartners)
			{
				if (Math.abs(this.length - _ds.length) > threshold)
				{
					this.removeCongruentPartner(_ds);
					_ds.removeCongruentPartner(this);
					changed = true;
				}
			}
			return changed;
		}
		/** Looks for all congruencies with this segment and checks if they are still valid, use threshold to determine congruency */
		public function checkExistingParallels(threshold:Number):Boolean
		{
			var changed:Boolean = false;
			for each (var _ds:DrawSegment in this.parallelPartners)
			{
				//trace (this.rotationAbs, _ds.rotationAbs, MathUtilities.calcAcuteRotationAbs(this.rotationAbs, _ds.rotationAbs)); 
				if (MathUtilities.calcAcuteRotationAbs(this.rotationAbs, _ds.rotationAbs) > threshold)
				{
					this.removeParallelPartner(_ds);
					_ds.removeParallelPartner(this);
					changed = true;
				}
			}
			return changed;
		}
		/** Applies congruency markings, one marking for each count. If zero, remove. */
		public function set congruentCount(count:uint):void
		{
			_congruentCount = count;
			if (_congruentCount == 0)
			{
				if (this.congruentMarking != null)
				{
					this.removeChild(congruentMarking);
					congruentMarking = null;
				}
			} else //greater than zero
			{
				if (this.congruentMarking == null)
				{
					congruentMarking = new Shape();
					this.addChild(congruentMarking);
				} else
				{
					congruentMarking.graphics.clear();
				}
				congruentMarking.graphics.lineStyle(2, 0x0000FF);
				// put this marking 2/3rds up the segment on right
				var i:uint
				if (this.angle >= 0)
				{
					for (i=0; i < congruentCount; i++)
					{
						congruentMarking.graphics.moveTo(-BACK_HEIGHT/2, -this.length*2/3+i*BACK_HEIGHT/2);
						congruentMarking.graphics.lineTo(BACK_HEIGHT/2, -this.length*2/3+i*BACK_HEIGHT/2);
					}
					congruentMarking.x = endpoint1.x;
					congruentMarking.y = endpoint1.y;
				} else 
				{
					for (i=0; i < congruentCount; i++)
					{
						congruentMarking.graphics.moveTo(-BACK_HEIGHT/2, -this.length*2/3+i*BACK_HEIGHT/2);
						congruentMarking.graphics.lineTo(BACK_HEIGHT/2, -this.length*2/3+i*BACK_HEIGHT/2);
					}
					congruentMarking.x = endpoint2.x;
					congruentMarking.y = endpoint2.y;
				}
				congruentMarking.rotation = rotationAbs;
			}
		}
		
		/** Applies parallel markings, one marking for each count. If zero, remove. */
		public function set parallelCount(count:uint):void
		{
			_parallelCount = count;
			if (_parallelCount == 0)
			{
				if (this.parallelMarking != null)
				{
					this.removeChild(parallelMarking);
					parallelMarking = null;
				}
			} else //greater than zero
			{
				if (this.parallelMarking == null)
				{
					parallelMarking = new Shape();
					this.addChild(parallelMarking)
				} else
				{
					parallelMarking.graphics.clear();
				}
				parallelMarking.graphics.lineStyle(2, 0x0000FF);
				// put this marking 2/3rds up the segment on right
				var i:uint;
				if (this.angle >= 0)
				{
					for (i=0; i < parallelCount; i++)
					{
						parallelMarking.graphics.moveTo(-BACK_HEIGHT/2, -this.length*1/3+i*BACK_HEIGHT/2);
						parallelMarking.graphics.lineTo(0, -this.length*1/3+(i-1)*BACK_HEIGHT/2);
						parallelMarking.graphics.lineTo(BACK_HEIGHT/2, -this.length*1/3+i*BACK_HEIGHT/2);
					}
					parallelMarking.x = endpoint1.x;
					parallelMarking.y = endpoint1.y;
				} else 
				{
					for (i=0; i < parallelCount; i++)
					{
						parallelMarking.graphics.moveTo(-BACK_HEIGHT/2, -this.length*1/3+i*BACK_HEIGHT/2);
						parallelMarking.graphics.lineTo(0, -this.length*1/3+(i-1)*BACK_HEIGHT/2);
						parallelMarking.graphics.lineTo(BACK_HEIGHT/2, -this.length*1/3+i*BACK_HEIGHT/2);
					}
					parallelMarking.x = endpoint2.x;
					parallelMarking.y = endpoint2.y;
				}
				parallelMarking.rotation = rotationAbs;
			}
		}
	}		
}